package fr.utbm.to52.android_bibtex.model.entry;

import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;

import my.javax.swing.event.EventListenerList;
import fr.utbm.to52.android_bibtex.model.spec.EntryDefinitionsEvent;
import fr.utbm.to52.android_bibtex.model.spec.EntryDefinitionsListener;

public class EntryList implements EntryListener, EntryDefinitionsListener {

	private static final EntryList INSTANCE = new EntryList();

	public static class KeyConflictException extends RuntimeException {

		/**
		 * 
		 */
		private static final long serialVersionUID = -8016409062536120614L;

		private final String key;

		public KeyConflictException(String key) {
			super("Entry key conflict in entry list for key: "+key);
			this.key = key;
		}

		public String getKey() {
			return this.key;
		}
	}

	/** key: {@link Entry#getKey()}.toLowerCase() */
	private final Map<String, Entry> entries = new TreeMap<String, Entry>();

	private final EventListenerList listeners = new EventListenerList();

	public static EntryList getInstance() {
		return INSTANCE;
	}

	private EntryList() {
	}

	public Map<String, Entry> getEntryList() {
		return Collections.unmodifiableMap(this.entries);
	}

	public Entry getEntry(String key) {
		return this.entries.get(key.toLowerCase());
	}

	public boolean containsEntry(String key) {
		return this.entries.containsKey(key.toLowerCase());
	}

	public boolean addEntry(Entry entry) {
		assert entry!=null;
		assert entry.getKey()!=null && !entry.getKey().isEmpty();

		String lowerCaseKey = entry.getKey().toLowerCase();
		if (!this.entries.containsKey(lowerCaseKey)) {
			this.entries.put(lowerCaseKey, entry);
			entry.addEntryListener(this);
			fireEntryAdded(entry);
			return true;
		}
		return false;
	}

	public boolean removeEntry(String key) {
		assert key!=null;

		String lowerCaseKey = key.toLowerCase();
		Entry entry = this.entries.get(lowerCaseKey);
		if (entry!=null) {
			entry.removeEntryListener(this);
			this.entries.remove(lowerCaseKey);
			fireEntryRemoved(entry);
			return true;
		}
		return false;
	}

	public void addEntryListListener(EntryListListener l) {
		assert l!=null;

		this.listeners.add(EntryListListener.class, l);
	}

	public void removeEntryListListener(EntryListListener l) {
		assert l!=null;

		this.listeners.remove(EntryListListener.class, l);
	}

	public EntryListListener[] getEntryListListeners() {
		return this.listeners.getListeners(EntryListListener.class);
	}

	protected void fireEntryAdded(Entry entry) {
		assert entry!=null;

		EntryListEvent event = new EntryListEvent(entry);
		for (EntryListListener listener : getEntryListListeners()) {
			listener.entryAdded(event);
		}
	}

	protected void fireEntryRemoved(Entry entry) {
		assert entry!=null;

		EntryListEvent event = new EntryListEvent(entry);
		for (EntryListListener listener : getEntryListListeners()) {
			listener.entryRemoved(event);
		}
	}

	protected void fireEntryChanged(EntryEvent changeEvent) {
		assert changeEvent!=null;

		EntryListEvent event = new EntryListEvent(changeEvent);
		for (EntryListListener listener : getEntryListListeners()) {
			listener.entryChanged(event);
		}
	}

	@Override
	public void entryChanged(EntryEvent event) {
		Entry entry = event.getSource();

		if (Entry.KEY_PROPERTY_NAME.equals(event.getPropertyName())) {
			// map's key changed!

			String oldKey = (String) event.getOldValue();
			String newKey = (String) event.getNewValue();

			assert oldKey!=null;
			assert newKey!=null;

			String oldLowerCaseKey = oldKey.toLowerCase();
			String newLowerCaseKey = newKey.toLowerCase();

			//assert this.entries.containsKey(oldLowerCaseKey);

			if (!oldLowerCaseKey.equals(newLowerCaseKey)) {
				this.entries.remove(oldLowerCaseKey);
				if (this.entries.containsKey(newLowerCaseKey)) {
					throw new KeyConflictException(newKey);
				}
				this.entries.put(newLowerCaseKey, entry);
			}
		} else {
			//assert this.entries.containsKey(entry.getKey().toLowerCase());

			if (Entry.DEFINITION_PROPERTY_NAME.equals(event.getPropertyName())) {
				// TODO handle entry's definition change
			} else if (Entry.FIELDS_PROPERTY_NAME.equals(event.getPropertyName())) {
				// TODO handle entry's fields change
			}

		}

		fireEntryChanged(event);
	}

	@Override
	public void definitionAdded(EntryDefinitionsEvent event) {
		// TODO Auto-generated method stub

	}

	@Override
	public void definitionRemoved(EntryDefinitionsEvent event) {
		// TODO Auto-generated method stub

	}

	@Override
	public void definitionChanged(EntryDefinitionsEvent event) {
		// TODO Auto-generated method stub

	}

}
